package dao

import (
	"context"
	"fmt"
	"strings"

	"github.com/qiniu/api.v7/v7/auth"
	"github.com/qiniu/api.v7/v7/storage"

	acc_pb "bilinadmin/api/pb/client"
	"bilinadmin/internal/object"
	"bilinadmin/pkg/lbs"
	"bilinadmin/pkg/rpc"
	"bilinadmin/pkg/util"

	open_pb "bilinadmin/api/pb/client/open/api/open/v1"
	pb "bilinadmin/api/pb/server"
	"github.com/tencentyun/qcloud-cos-sts-sdk/go"
	"time"
)

// BilinVendorDao 比邻其他服务
type BilinVendorDao interface {
	// OauthWechatMP oauth 用户
	OauthWechatMP(ctx context.Context, code string) (string, error)
	// GetWechatMPJsParams 查询JS config 参数
	GetWechatMPJsParams(ctx context.Context, uri string) (*open_pb.GetWechatMPJsParamsReply, error)
	// SendAccConfirmCode 发送统一账号确认短信
	SendAccConfirmCode(ctx context.Context, mobile, codeType string) error
	// AccConfirmInvitation 账号邀请确认
	AccConfirmInvitation(ctx context.Context, gId, openId, unionId, nickname, avatar, code, codeType string) (string, error)
	// EmpAccountInvitation 员工统一账号邀请
	EmpAccountInvitation(ctx context.Context, mobile, email, userName string) (*object.AccountInfo, error)
	// LocationToAddress 坐标转地址
	LocationToAddress(ctx context.Context, lat, lng float64) (*object.AddressInfo, error)
	// AddressToLocation 地址坐标解析
	AddressToLocation(ctx context.Context, address, region string) (*object.LocationInfo, error)
	// AddressSearch 地址搜索
	AddressSearch(ctx context.Context, keyword string, lat, lng float64) ([]*object.LocationInfo, error)
	// GetDistanceOfLocations 获取地址距离
	GetDistanceOfLocations(ctx context.Context, from *object.Location, to []*object.Location) ([]float64, error)
	// GetQiqiuUploadToken 获取七牛上传token
	GetQiqiuUploadToken(_ context.Context, bucket, overwriteKey string, expires uint64) (string, uint64)
	// GetCosAuthorization 获取cos上传 authorization
	GetCosAuthorization(_ context.Context, bucket, region string) (*pb.CosAuthorizationCredentials, error)
}

var vDao BilinVendorDao

type vDaoImpl struct {
	*DaoBase
	accClientPool  *rpc.Pool
	openClientPool *rpc.Pool
	name           string // 表名
}

// InitBilinVendorDao 初始化 brand dao
func InitBilinVendorDao(accPool, openPool *rpc.Pool) {
	vDao = &vDaoImpl{
		DaoBase:        daoBase,
		accClientPool:  accPool,
		openClientPool: openPool,
		name:           "brand",
	}
}

// GetBilinVendorDao get bilin vendor dao
func GetBilinVendorDao() BilinVendorDao {
	return vDao
}

// OauthWechatMP open 微信公众号授权
func (dao *vDaoImpl) OauthWechatMP(ctx context.Context, code string) (string, error) {
	req := &open_pb.OauthWechatMPRequest{
		AppId: dao.cfg.Platform.MpAppId,
		Code:  code,
	}
	conn, err := dao.openClientPool.Get(ctx)
	if err != nil {
		return "", err
	}
	defer func() {
		_ = conn.Close()
	}()
	client := open_pb.NewOpenServiceClient(conn.ClientConn)
	resp, err := client.OauthWechatMP(ctx, req)
	if err != nil {
		return "", err
	}
	return resp.OpenId, nil
}

// GetWechatMPJsParams 查询JS config 参数
func (dao *vDaoImpl) GetWechatMPJsParams(ctx context.Context, uri string) (*open_pb.GetWechatMPJsParamsReply, error) {
	req := &open_pb.GetWechatMPJsParamsRequest{
		AppId: dao.cfg.Platform.MpAppId,
		Url:   uri,
	}
	conn, err := dao.openClientPool.Get(ctx)
	if err != nil {
		return nil, err
	}
	defer func() {
		_ = conn.Close()
	}()
	client := open_pb.NewOpenServiceClient(conn.ClientConn)
	return client.GetWechatMPJsParams(ctx, req)
}

// SendAccConfirmCode 发送统一账号确认短信
func (dao *vDaoImpl) SendAccConfirmCode(ctx context.Context, mobile, codeType string) error {
	req := &acc_pb.SendMobileCodeReq{
		Mobile:      mobile,
		CodeType:    codeType,
		CountryCode: "86",
	}
	conn, err := dao.accClientPool.Get(ctx)
	if err != nil {
		return err
	}
	defer func() {
		_ = conn.Close()
	}()
	client := acc_pb.NewLdAccountClient(conn.ClientConn)
	_, err = client.SendMobileCode(ctx, req)
	return err
}

// AccConfirmInvitation 账号邀请确认
func (dao *vDaoImpl) AccConfirmInvitation(ctx context.Context, gId, openId, unionId, nickname, avatar, code, codeType string) (string, error) {
	req := &acc_pb.ConfirmInvitationReq{
		GId:        gId,
		OpenId:     openId,
		UnionId:    unionId,
		Nickname:   nickname,
		Avatar:     avatar,
		Code:       code,
		CodeType:   codeType,
		CodeOrigin: "mobile",
		Platform:   "wechat_mp",
	}
	conn, err := dao.accClientPool.Get(ctx)
	if err != nil {
		return "", err
	}
	defer func() {
		_ = conn.Close()
	}()
	client := acc_pb.NewLdAccountClient(conn.ClientConn)
	res, err := client.AccConfirmInvitation(ctx, req)
	if err != nil {
		return "", err
	}
	return res.GId, nil
}

// EmpAccountInvitation 员工统一账号邀请
func (dao *vDaoImpl) EmpAccountInvitation(ctx context.Context, mobile, email, userName string) (*object.AccountInfo, error) {
	req := &acc_pb.InviteAccountReq{
		Mobile:      mobile,
		Email:       email,
		UserName:    userName,
		CountryCode: "86",
		Origin:      "bilin-admin",
	}
	rawStr := fmt.Sprintf("%s%s%s%s%s%s", req.UserName, req.Mobile, req.Email, req.CountryCode, req.Origin, dao.cfg.Keys.Secret)
	var err error
	req.Sign, err = util.SignSha256(rawStr)
	if err != nil {
		return nil, err
	}
	conn, err := dao.accClientPool.Get(ctx)
	if err != nil {
		return nil, err
	}
	defer func() {
		_ = conn.Close()
	}()
	client := acc_pb.NewLdAccountClient(conn.ClientConn)
	resp, err := client.AccountInvitation(ctx, req)
	if err != nil {
		return nil, err
	}
	return &object.AccountInfo{
		GID:      resp.GId,
		Mobile:   resp.Mobile,
		Email:    resp.Email,
		UserName: resp.UserName,
		Modified: resp.Modified,
		Status:   resp.Status,
	}, nil
}

// LocationToAddress 坐标转地址
func (dao *vDaoImpl) LocationToAddress(ctx context.Context, lat, lng float64) (*object.AddressInfo, error) {
	client := lbs.NewQQLbsClient(dao.cfg.Keys.QQLbsKey, dao.cfg.Keys.QQLbsSecret)
	req := &lbs.GeoAddressCoderReq{
		Location: &lbs.Location{
			Lat: lat,
			Lng: lng,
		},
		GetPoi: false,
	}
	res, err := client.GetAddressByLocation(ctx, req)
	if err != nil {
		return nil, err
	}
	ret := &object.AddressInfo{
		CityCode:     res.Result.AdInfo.GetCityCode(),
		DistrictCode: res.Result.AdInfo.AdCode,
		Address:      res.Result.Address,
		Nation:       res.Result.AddressComponent.Nation,
		Province:     res.Result.AddressComponent.Province,
		City:         res.Result.AddressComponent.City,
		District:     res.Result.AddressComponent.District,
		Street:       res.Result.AddressComponent.Street,
		StreetNo:     res.Result.AddressComponent.StreetNumber,
	}
	return ret, nil
}

// AddressSearch 地址搜索
func (dao *vDaoImpl) AddressSearch(ctx context.Context, keyword string, lat, lng float64) ([]*object.LocationInfo, error) {
	client := lbs.NewQQLbsClient(dao.cfg.Keys.QQLbsKey, dao.cfg.Keys.QQLbsSecret)
	req := &lbs.PlaceSearchReq{
		Keyword: keyword,
		Boundary: &lbs.Boundary{
			Lat:        lat,
			Lng:        lng,
			Radius:     100,
			AutoExtend: true,
		},
		OrderBy: "_distance",
	}
	res, err := client.PlaceSearch(ctx, req)
	if err != nil {
		return nil, err
	}
	locs := make([]*object.LocationInfo, len(res.Data))
	for idx, l := range res.Data {
		locs[idx] = &object.LocationInfo{
			Title:     l.Title,
			Latitude:  l.Location.Lat,
			Longitude: l.Location.Lng,
			CityCode:  l.AdInfo.GetCode(),
			Address:   l.Address,
			Province:  l.AdInfo.Province,
			City:      l.AdInfo.City,
			District:  l.AdInfo.District,
			Distance:  l.Distance,
			Category:  l.Category,
		}
	}
	return locs, nil
}

// AddressToLocation 地址坐标解析
func (dao *vDaoImpl) AddressToLocation(ctx context.Context, address, region string) (*object.LocationInfo, error) {
	client := lbs.NewQQLbsClient(dao.cfg.Keys.QQLbsKey, dao.cfg.Keys.QQLbsSecret)
	if !strings.HasPrefix(address, region) {
		address = fmt.Sprintf("%s%s", region, address)
	}
	req := &lbs.GeoLocationCoderReq{
		Address: address,
		Region:  region,
	}
	res, err := client.GetLocationByAddress(ctx, req)
	if err != nil {
		if err.Error() == "查询无结果" {
			return nil, nil
		}
		return nil, err
	}
	return &object.LocationInfo{
		Title:     res.Result.Title,
		Latitude:  res.Result.Location.Lat,
		Longitude: res.Result.Location.Lng,
		CityCode:  res.Result.AdInfo.AdCode,
		Province:  res.Result.AddressComponents.Province,
		City:      res.Result.AddressComponents.City,
		District:  res.Result.AddressComponents.District,
		Street:    res.Result.AddressComponents.Street,
		StreetNo:  res.Result.AddressComponents.StreetNumber,
	}, nil
}

// GetDistanceOfLocations 获取地址距离
func (dao *vDaoImpl) GetDistanceOfLocations(ctx context.Context, from *object.Location, tos []*object.Location) ([]float64, error) {
	client := lbs.NewQQLbsClient(dao.cfg.Keys.QQLbsKey, dao.cfg.Keys.QQLbsSecret)
	req := &lbs.DistanceMatrixReq{
		Mode: "walking",
	}
	fromArr := &lbs.LocationArray{Locations: []*lbs.Location{{Lat: from.Lat, Lng: from.Lng}}}
	toArr := make([]*lbs.Location, len(tos))
	for idx, to := range tos {
		toArr[idx] = &lbs.Location{
			Lat: to.Lat,
			Lng: to.Lng,
		}
	}
	req.From = fromArr
	// 距离数组
	distances := make([]*lbs.DistanceMatrixItem, len(tos))
	// 分批请求
	step := 20
	start := 0
	num := len(toArr)
	for start < num {
		end := start + step
		if end > num {
			end = num
		}
		req.To = &lbs.LocationArray{Locations: toArr[start:end]}
		res, err := client.DistanceMatrix(ctx, req)
		if err != nil {
			return nil, err
		}
		if len(res.Result.Rows) == 0 {
			continue
		}
		for idx, elem := range res.Result.Rows[0].Elements {
			distances[start+idx] = elem
		}
		start = end
	}

	ret := make([]float64, len(tos))
	for idx, d := range distances {
		ret[idx] = d.Distance
	}
	return ret, nil
}

// GetQiqiuUploadToken 获取七牛上传token
func (dao *vDaoImpl) GetQiqiuUploadToken(_ context.Context, bucket, overwriteKey string, expires uint64) (string, uint64) {
	if expires == 0 {
		expires = 7200
	}
	if overwriteKey != "" {
		bucket = fmt.Sprintf("%s:%s", bucket, overwriteKey)
	}
	mac := auth.New(dao.cfg.Keys.QiniuAccessKey, dao.cfg.Keys.QiniuSecretKey)
	putPolicy := storage.PutPolicy{
		Scope:   bucket,
		Expires: expires,
	}
	uToken := putPolicy.UploadToken(mac)
	return uToken, expires
}

// GetCosAuthorization 获取cos上传 authorization
func (dao *vDaoImpl) GetCosAuthorization(_ context.Context, bucket, region string) (*pb.CosAuthorizationCredentials, error) {

	appid := dao.cfg.Cos.AppID

	c := sts.NewClient(
		dao.cfg.Cos.SecretID,
		dao.cfg.Cos.SecretKey,
		nil,
	)
	opt := &sts.CredentialOptions{
		DurationSeconds: int64(time.Hour.Seconds()),
		Region:          region,
		Policy: &sts.CredentialPolicy{
			Statement: []sts.CredentialPolicyStatement{
				{
					Action: []string{
						// "name/cos:PostObject",
						// "name/cos:PutObject",
						"name/cos:*",
					},
					Effect: "allow",
					Resource: []string{
						//这里改成允许的路径前缀，可以根据自己网站的用户登录态判断允许上传的具体路径，例子： a.jpg 或者 a/* 或者 * (使用通配符*存在重大安全风险, 请谨慎评估使用)
						"qcs::cos:" + region + ":uid/" + appid + ":" + bucket + "/*",
					},
				},
			},
		},
	}
	res, err := c.GetCredential(opt)
	if err != nil {
		return nil, err
	}
	credentialsPtr := &pb.CosAuthorizationCredentials{
		TmpSecretId:  res.Credentials.TmpSecretID,
		TmpSecretKey: res.Credentials.TmpSecretKey,
		SessionToken: res.Credentials.SessionToken,
		ExpiredTime:  uint64(res.ExpiredTime),
	}
	return credentialsPtr, nil
}
